package org.alino.core.shiro.filter;

import org.alino.core.shiro.cache.VCache;
import org.alino.core.shiro.session.ShiroSessionRepository;
import org.alino.core.shiro.token.TokenManager;
import org.alino.core.utils.JsonUtils;
import org.alino.core.utils.LoggerUtils;
import org.alino.core.utils.RequestUtils;
import org.alino.core.utils.ReturnUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.LinkedList;

/**
 * @Desc KickoutSessionControlFilter redis版本
 * @Author by Brant
 * @Date 2017/1/17
 */
public class KickoutSessionControlFilter extends AccessControlFilter {
    //静态注入
    private String kickoutUrl;
    //在线用户
    final static String ONLINE_USER = KickoutSessionControlFilter.class.getCanonicalName()+ "_online_user";
    //踢出状态，true踢出
    final static String KICKOUT_STATUS = KickoutSessionControlFilter.class.getCanonicalName()+ "_kickout_status";
    //缓存
    private VCache cache;
    private int maxSession = 1;
    //踢出之前登录的false/之后登录的用户true
    private boolean kickoutProperties = false;
    private int setJedisCacheTimer = 3600;
    //session获取
    private ShiroSessionRepository shiroSessionRepository;

    private LinkedHashMap<Long, Deque<Serializable>> infoMap;

    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object mappedValue) throws Exception {
        HttpServletRequest request = ((HttpServletRequest)servletRequest);
        String url = request.getRequestURI();
        Subject subject = getSubject(request, servletResponse);
        //如果是相关目录 or 如果没有登录 就直接return true
        if(url.startsWith("/open/") || (!subject.isAuthenticated() && !subject.isRemembered())){
            return Boolean.TRUE;
        }
        Session session = subject.getSession();
        Serializable sessionId = session.getId();
        //获取tokenId
        Long userId = TokenManager.getUserId();

        //从缓存获取用户-Session信息 <UserId,SessionId>
        LinkedHashMap<Long, Deque<Serializable>> infoMap = cache.get(ONLINE_USER, LinkedHashMap.class);
        //如果不存在，创建一个新的
        infoMap = null == infoMap ? new LinkedHashMap<Long, Deque<Serializable>>() : infoMap;

        Deque<Serializable> deque = infoMap.get(userId);
        if(deque == null){
            deque = new LinkedList<>();
            infoMap.put(userId,deque);
        }
        if(!deque.contains(sessionId) && session.getAttribute(KICKOUT_STATUS)==null){
            deque.push(sessionId);
            cache.setex(ONLINE_USER, infoMap, setJedisCacheTimer);
        }
        while(deque.size() > maxSession){
            Serializable kickoutSessionId = null;
            if(kickoutProperties){
                //踢出之后登录的用户
                kickoutSessionId = deque.removeFirst();
            }else{
                //踢出之前登录的
                kickoutSessionId = deque.removeLast();
            }
            try{
                Session kickoutSession = shiroSessionRepository.getSession(kickoutSessionId);
                //标记session已经踢出
                kickoutSession.setAttribute(KICKOUT_STATUS, true);
                shiroSessionRepository.saveSession(kickoutSession);//更新session
                LoggerUtils.debug(getClass(), "kickout old session success,oldId[%s]",kickoutSessionId);
            } catch (NullPointerException ne){
                LoggerUtils.debug(getClass(), ne.getMessage());
            } catch (Exception ex){
                LoggerUtils.debug(getClass(), ex.getMessage());
            }finally {
                shiroSessionRepository.deleteSession(kickoutSessionId);
                deque.remove(kickoutSessionId);
                //存储到缓存1个小时（这个时间最好和session的有效期一致或者大于session的有效期）
                cache.setex(ONLINE_USER, infoMap, setJedisCacheTimer);
            }
        }
        /**
         * 判断是否已经踢出
         * 1.如果是Ajax 访问，那么给予json返回值提示。
         * 2.如果是普通请求，直接跳转到登录页
         */
        if (session.getAttribute(KICKOUT_STATUS)!=null) {
            //判断是不是Ajax请求
            if (RequestUtils.isAjaxRequest(request) ) {
                LoggerUtils.debug(getClass(), "账号已在其他地方登陆，请重新登陆，Ajax请求！");
                JsonUtils.write((HttpServletResponse) servletResponse, ReturnUtils.OTHER_LOGIN);
            }
            return  Boolean.FALSE;
        }
        LoggerUtils.info(getClass(),"==============================================================kickout===========>>>"+infoMap.get(userId).size());
        return true;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        //先退出
        Subject subject = getSubject(request, response);
        subject.logout();
        WebUtils.getSavedRequest(request);
        //再重定向
        WebUtils.issueRedirect(request, response,kickoutUrl);
        return false;
    }

    public String getKickoutUrl() {
        return kickoutUrl;
    }

    public void setKickoutUrl(String kickoutUrl) {
        this.kickoutUrl = kickoutUrl;
    }

    public VCache getCache() {
        return cache;
    }

    public void setCache(VCache cache) {
        this.cache = cache;
    }

    public ShiroSessionRepository getShiroSessionRepository() {
        return shiroSessionRepository;
    }

    public void setShiroSessionRepository(ShiroSessionRepository shiroSessionRepository) {
        this.shiroSessionRepository = shiroSessionRepository;
    }

    public int getMaxSession() {
        return maxSession;
    }

    public void setMaxSession(int maxSession) {
        this.maxSession = maxSession;
    }

    public boolean isKickoutProperties() {
        return kickoutProperties;
    }

    public void setKickoutProperties(boolean kickoutProperties) {
        this.kickoutProperties = kickoutProperties;
    }

    public int getSetJedisCacheTimer() {
        return setJedisCacheTimer;
    }

    public void setSetJedisCacheTimer(int setJedisCacheTimer) {
        this.setJedisCacheTimer = setJedisCacheTimer;
    }
}
